import requests
from abc import ABCMeta, abstractmethod
import time
import json
from v3.Error import *
from requests.exceptions import SSLError, ConnectTimeout, HTTPError
from hashlib import md5
from threading import Lock
import logging
logger = logging.getLogger()


class Downloader(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def download(self, request_item, **kwargs):
        pass


class Filter(object):
    m5 = md5()
    result_md5_set = set()

    def __init__(self):
        self.lock = Lock()

    def filter(self, response):
        """
        filter对结果进行过滤去重, 利用数据算出md5值, 如果已经存在相同数据, 则不需要将请求结果加入回应队列
        :param response: 请求结果 requests.Response
        :return: None
        """
        try:
            self.lock.acquire()
            self.m5.update(response.content)
            hexdigest = self.m5.hexdigest()
            if hexdigest not in self.result_md5_set:
                self.result_md5_set.add(hexdigest)
                return response.text
            else:
                return None
        finally:
            self.lock.release()


class DefaultDownloader(Downloader):
    def __init__(self, sock_puppet):
        self.sockpuppet = sock_puppet
        pass

    def download(self, request_item, **kwargs):
        """
        下载器
        :param sleep_time: 线程睡眠时间
        :param request_item: 请求体
        :return: 请求结果(requests.Response)
        """
        if self.sockpuppet.has_headers() is False:
            return None, -1
        headers, i = self.sockpuppet.get_headers()
        sleep_time = kwargs.get('sleep_time')
        method = request_item.get()['method'].upper()
        retry_times = request_item.get()['retry_times']
        url = request_item.get()['url']
        timeout = request_item.get()['timeout']
        params = request_item.get()['params']
        request_body = request_item.get()['request_body']
        req = requests

        while retry_times > 0:
            try:
                if method == 'GET':
                    logger.info('发送get请求, url=' + url + ' 剩余尝试次数: ' + str(retry_times))
                    res = req.get(url,
                              params=params,
                              headers=headers,
                              timeout=3,
                              allow_redirects=False)
                    if res.status_code == 302:
                        raise ForbiddenError(request_item=request_item)
                    time.sleep(sleep_time)
                    return res, i
                elif method == 'POST':
                    res = req.post(url,
                                        data=json.dumps(request_body),
                                        headers=headers,
                                        timeout=timeout,
                                        verify=request_item.verify)
                    time.sleep(sleep_time)
                    return res, i
            except ForbiddenError as e:
                logger.warning('headers[' + str(i) + '] 已经失效, 尝试更换')
                self.sockpuppet.set_headers_invalid(i)
                if self.sockpuppet.has_headers():
                    headers, i = self.sockpuppet.get_headers()
                else:
                    return None, -1
            except ConnectTimeout as e:
                logger.warning('request timeout')
                retry_times -= 1
            except HTTPError as e:
                print(e)
            except SSLError as e:
                retry_times -= 1
                logger.warning('SSL证书验证失败, 尝试关闭证书验证, 剩余尝试次数: ' + str(retry_times))
                request_item.verify = False
            except Exception as e:
                retry_times = 0
                logger.error(e)
                raise e
        if retry_times == 0:
            raise TimeoutError(request_item=request_item)